﻿namespace NoIdeaIoc;
public class WebFileProvider : IFileProvider, IDisposable
{
    private object _fileWatcherLock = new object();
    private const string PollingEnvironmentKey = "DOTNET_USE_POLLING_FILE_WATCHER";
    private readonly ExclusionFilters _filters;
    private readonly Func<PhysicalFilesWatcher> _fileWatcherFactory;
    private PhysicalFilesWatcher _fileWatcher;
    private bool _fileWatcherInitialized;
    private bool? _usePollingFileWatcher;
    private bool? _useActivePolling;

    public WebFileProvider(string root)
      : this(root, ExclusionFilters.Sensitive)
    {
    }

    public WebFileProvider(string root, ExclusionFilters filters)
    {
        if (!Path.IsPathRooted(root))
            throw new ArgumentException("The path must be absolute.", nameof(root));
        this.Root = PathUtils.EnsureTrailingSlash(Path.GetFullPath(root));
        if (!Directory.Exists(this.Root))
            throw new DirectoryNotFoundException(this.Root);
        this._filters = filters;
        this._fileWatcherFactory = (Func<PhysicalFilesWatcher>)(() => this.CreateFileWatcher());
    }

    public bool UsePollingFileWatcher
    {
        get
        {
            if (this._fileWatcher != null)
                throw new InvalidOperationException("Cannot modify UsePollingFileWatcher once file watcher has been initialized.");
            if (!this._usePollingFileWatcher.HasValue)
                this.ReadPollingEnvironmentVariables();
            return this._usePollingFileWatcher.Value;
        }
        set
        {
            this._usePollingFileWatcher = new bool?(value);
        }
    }

    public bool UseActivePolling
    {
        get
        {
            if (!this._useActivePolling.HasValue)
                this.ReadPollingEnvironmentVariables();
            return this._useActivePolling.Value;
        }
        set
        {
            this._useActivePolling = new bool?(value);
        }
    }

    internal PhysicalFilesWatcher FileWatcher
    {
        get
        {
            return LazyInitializer.EnsureInitialized<PhysicalFilesWatcher>(ref this._fileWatcher, ref this._fileWatcherInitialized, ref this._fileWatcherLock, this._fileWatcherFactory);
        }
        set
        {
            this._fileWatcherInitialized = true;
            this._fileWatcher = value;
        }
    }

    internal PhysicalFilesWatcher CreateFileWatcher()
    {
        string str = PathUtils.EnsureTrailingSlash(Path.GetFullPath(this.Root));
        return new PhysicalFilesWatcher(str, new FileSystemWatcher(str), this.UsePollingFileWatcher, this._filters);
    }

    private void ReadPollingEnvironmentVariables()
    {
        string environmentVariable = Environment.GetEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER");
        bool flag = string.Equals(environmentVariable, "1", StringComparison.Ordinal) || string.Equals(environmentVariable, "true", StringComparison.OrdinalIgnoreCase);
        this._usePollingFileWatcher = new bool?(flag);
        this._useActivePolling = new bool?(flag);
    }
    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
        this.Dispose(true);
    }
    /// <summary>
    /// 释放资源
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        this._fileWatcher?.Dispose();
    }
    /// <summary>
    /// 析构函数
    /// </summary>
    ~WebFileProvider()
    {
        this.Dispose(false);
    }
    /// <summary>
    /// 根目录
    /// </summary>
    public string Root { get; }

    private string GetFullPath(string path)
    {
        if (PathUtils.PathNavigatesAboveRoot(path))
            return (string)null;
        string fullPath;
        try
        {
            fullPath = Path.GetFullPath(Path.Combine(this.Root, path));
        }
        catch
        {
            return (string)null;
        }
        if (!this.IsUnderneathRoot(fullPath))
            return (string)null;
        return fullPath;
    }

    private bool IsUnderneathRoot(string fullPath)
    {
        return fullPath.StartsWith(this.Root, StringComparison.OrdinalIgnoreCase);
    }
    /// <summary>
    /// 根据路径信息获取IFileInfo
    /// </summary>
    /// <param name="subpath"></param>
    /// <returns></returns>
    public IFileInfo GetFileInfo(string subpath)
    {
        if (string.IsNullOrEmpty(subpath) || PathUtils.HasInvalidPathChars(subpath))
            return (IFileInfo)new NotFoundFileInfo(subpath);
        subpath = subpath.TrimStart(PathUtils._pathSeparators);
        if (Path.IsPathRooted(subpath))
            return (IFileInfo)new NotFoundFileInfo(subpath);
        string fullPath = this.GetFullPath(subpath);
        if (fullPath == null)
            return (IFileInfo)new NotFoundFileInfo(subpath);
        FileInfo info = new FileInfo(fullPath);
        if (IsExcluded((FileSystemInfo)info, this._filters))
            return (IFileInfo)new NotFoundFileInfo(subpath);
        return (IFileInfo)new PhysicalFileInfo(info);
    }
    static bool IsExcluded(FileSystemInfo fileSystemInfo, ExclusionFilters filters)
    {
        return filters != ExclusionFilters.None && (fileSystemInfo.Name.StartsWith(".", StringComparison.Ordinal) && (filters & ExclusionFilters.DotPrefixed) != ExclusionFilters.None || fileSystemInfo.Exists && ((fileSystemInfo.Attributes & FileAttributes.Hidden) != (FileAttributes)0 && (filters & ExclusionFilters.Hidden) != ExclusionFilters.None || (fileSystemInfo.Attributes & FileAttributes.System) != (FileAttributes)0 && (filters & ExclusionFilters.System) != ExclusionFilters.None));
    }
    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        try
        {
            if (subpath == null || PathUtils.HasInvalidPathChars(subpath))
                return (IDirectoryContents)NotFoundDirectoryContents.Singleton;
            subpath = subpath.TrimStart(PathUtils._pathSeparators);
            if (Path.IsPathRooted(subpath))
                return (IDirectoryContents)NotFoundDirectoryContents.Singleton;
            string fullPath = this.GetFullPath(subpath);
            if (fullPath == null || !Directory.Exists(fullPath))
                return (IDirectoryContents)NotFoundDirectoryContents.Singleton;
            return (IDirectoryContents)new PhysicalDirectoryContents(fullPath, this._filters);
        }
        catch (DirectoryNotFoundException ex)
        {
        }
        catch (IOException ex)
        {
        }
        return (IDirectoryContents)NotFoundDirectoryContents.Singleton;
    }

    public IChangeToken Watch(string filter)
    {
        if (filter == null || PathUtils.HasInvalidFilterChars(filter))
            return (IChangeToken)NullChangeToken.Singleton;
        filter = filter.TrimStart(PathUtils._pathSeparators);
        return this.FileWatcher.CreateFileChangeToken(filter);
    }
}

internal static class PathUtils
{
    private static readonly char[] _invalidFileNameChars = ((IEnumerable<char>)Path.GetInvalidFileNameChars()).Where<char>((Func<char, bool>)(c =>
    {
        if ((int)c != (int)Path.DirectorySeparatorChar)
            return (int)c != (int)Path.AltDirectorySeparatorChar;
        return false;
    })).ToArray<char>();
    private static readonly char[] _invalidFilterChars = ((IEnumerable<char>)PathUtils._invalidFileNameChars).Where<char>((Func<char, bool>)(c =>
    {
        if (c != '*' && c != '|')
            return c != '?';
        return false;
    })).ToArray<char>();
    public static readonly char[] _pathSeparators = new char[2]
    {
        Path.DirectorySeparatorChar,
        Path.AltDirectorySeparatorChar
    };

    internal static bool HasInvalidPathChars(string path)
    {
        return path.IndexOfAny(PathUtils._invalidFileNameChars) != -1;
    }

    internal static bool HasInvalidFilterChars(string path)
    {
        return path.IndexOfAny(PathUtils._invalidFilterChars) != -1;
    }

    internal static string EnsureTrailingSlash(string path)
    {
        if (!string.IsNullOrEmpty(path) && (int)path[path.Length - 1] != (int)Path.DirectorySeparatorChar)
            return path + Path.DirectorySeparatorChar.ToString();
        return path;
    }

    internal static bool PathNavigatesAboveRoot(string path)
    {
        StringTokenizer stringTokenizer = new StringTokenizer(path, PathUtils._pathSeparators);
        int num = 0;
        foreach (StringSegment stringSegment in stringTokenizer)
        {
            if (!stringSegment.Equals(".") && !stringSegment.Equals(""))
            {
                if (stringSegment.Equals(".."))
                {
                    --num;
                    if (num == -1)
                        return true;
                }
                else
                    ++num;
            }
        }
        return false;
    }
}